/*
 * FileTable.cpp
 *
 *  Created on: 27/06/2014
 *      Author: Vitor
 */

#include "FileTable.h"

FileTable::FileTable() :
		filesystem_root(new Directory("files/", nullptr)) {
}

FileTable::FileTable(Directory* directory,
		std::map<std::string, FileDescriptor*> file_table) :
		filesystem_root(directory), file_table(file_table) {
}

FileTable::~FileTable() {
	delete (filesystem_root);
}

void FileTable::make_directory(std::string full_path) {
	remove_initial_slash(full_path);
	portable_mkdir(full_path);
	auto parent_directory = filesystem_root->get_element_from_path(
			parent_path(full_path));
	((Directory*) parent_directory)->add_directory(
			new Directory(full_path, (Directory*) parent_directory));
}

void FileTable::remove_directory(std::string full_path) {
	remove_initial_slash(full_path);
	portable_rmdir(full_path);
	auto parent_directory = filesystem_root->get_element_from_path(
			parent_path(full_path));
	((Directory*) parent_directory)->delete_element(full_path);

	auto it = file_table.begin();
	while (it != file_table.end()) {
		if ((it->first.find(full_path)) != std::string::npos) {
			file_table.erase(it++);
		} else {
			it++;
		}
	}
}

void FileTable::rename_directory(std::string full_path,
		std::string new_full_path) {
	remove_initial_slash(full_path);
	remove_initial_slash(new_full_path);
	auto directory = filesystem_root->get_element_from_path(full_path);
	std::vector<FileDescriptor*> reinsert_vector;

	portable_rename(full_path, new_full_path);
	directory->rename(full_path, new_full_path);

	std::map<std::string, FileDescriptor*> new_file_table;
	for (auto it = file_table.begin(); it != file_table.end(); it++) {
		new_file_table[it->second->get_name()] = it->second;
	}
	file_table = new_file_table;
}

void FileTable::add_file(FileDescriptor* new_file) {
	remove_initial_slash(new_file->get_name());
	auto parent_directory = filesystem_root->get_element_from_path(
			parent_path(new_file->get_name()));

	((Directory*) parent_directory)->add_file(new_file);
	file_table[new_file->get_name()] = new_file;
}

void FileTable::add_file(FileDescriptor* new_file,
		std::vector<char> file_contents) {
	remove_initial_slash(new_file->get_name());
	auto parent_directory = filesystem_root->get_element_from_path(
			parent_path(new_file->get_name()));

	File new_physical_file(new_file->get_name(), "wb");
	if (!new_physical_file.exists()) {
		fprintf(stderr, "Nao consegui criar o arquivo %s.\n",
				new_file->get_name().c_str());
		return;
	}

	new_physical_file.binary_write(file_contents);
	((Directory*) parent_directory)->add_file(new_file);
	file_table[new_file->get_name()] = new_file;
}

void FileTable::remove_file(std::string full_path) {
	remove_initial_slash(full_path);
	auto parent_directory = filesystem_root->get_element_from_path(
			parent_path(full_path));
	portable_rmfile(full_path);
	((Directory*) parent_directory)->delete_element(full_path);
	file_table.erase(file_table.find(full_path));
}

void FileTable::rename_file(std::string full_path, std::string new_full_path) {
	remove_initial_slash(full_path);
	remove_initial_slash(new_full_path);
	filesystem_root->get_element_from_path(full_path)->set_name(new_full_path);

	portable_rename(full_path, new_full_path);

	auto temp_file = file_table[full_path];
	file_table.erase(file_table.find(full_path));
	file_table[new_full_path] = temp_file;
}

FileSystemElement* FileTable::get_element_from_path(std::string full_path) {
	remove_initial_slash(full_path);
	return filesystem_root->get_element_from_path(full_path);
}

std::string FileTable::parent_path(std::string path) {
	// Diret�rio
	if (ends_with(path, "/")) {
		path = path.substr(0, path.length() - 1);
	}
	// Sen�o, arquivo
	auto found = path.find_last_of("/");
	return path.substr(0, found + 1);
}

bool FileTable::ends_with(std::string full_string, std::string ending) {
	if (full_string.length() >= ending.length()) {
		return (0
				== full_string.compare(full_string.length() - ending.length(),
						ending.length(), ending));
	} else {
		return false;
	}
}

ByteQueue FileTable::serialize() {
	return filesystem_root->serialize();
}

FileTable* FileTable::deserialize(ByteQueue& bytes) {
	std::map<std::string, FileDescriptor*> file_table;
	bytes.pop<char>();
	auto new_root = deserialize_directory(bytes, nullptr, file_table);
	return new FileTable(new_root, file_table);
}

Directory* FileTable::deserialize_directory(ByteQueue& bytes, Directory* parent,
		std::map<std::string, FileDescriptor*>& file_table) {
	Directory* new_directory;
	auto directory_name = bytes.pop<std::string>();
	new_directory = new Directory(directory_name, parent);

	if (!directory_exists(directory_name)) {
		portable_mkdir(directory_name);
	}

	char type;
	while ((type = bytes.pop<char>()) != FileSystemElement::END_OF_CHILDREN) {
		switch (type) {
		case FileSystemElement::DIRECTORY:
			new_directory->add_directory(
					deserialize_directory(bytes, new_directory, file_table));
			break;
		case FileSystemElement::FILE_DESCRIPTOR:
			new_directory->add_file(
					deserialize_file_descriptor(bytes, file_table));
			break;
		default:
			fprintf(stderr,
					"Erro em deserialize FileTable, tipo %d nao reconhecido.\n",
					type);
			exit(1);
		}
	}
	return new_directory;
}

FileDescriptor* FileTable::deserialize_file_descriptor(ByteQueue& bytes,
		std::map<std::string, FileDescriptor*>& file_table) {
	FileDescriptor* new_file_descriptor;

	auto file_name = bytes.pop<std::string>();
	auto location = bytes.pop<unsigned int>();
	auto copy_location = bytes.pop<unsigned int>();
	auto size = bytes.pop<unsigned int>();

	new_file_descriptor = new FileDescriptor(file_name, location, copy_location,
			size);
	file_table[new_file_descriptor->get_name()] = new_file_descriptor;

	return new_file_descriptor;
}

void FileTable::get_file_count(std::map<unsigned int, int>& file_count) {

	for (auto it = file_table.begin(); it != file_table.end(); ++it) {
		file_count[it->second->get_location()] += 1;
		file_count[it->second->get_copy_location()] += 1;
	}
}

unsigned int FileTable::get_top_peer(std::map<unsigned int, int>& file_count) {

	int top_ip, top_files_count;

	top_files_count = 0;
	top_ip = 0;

	for (auto it = file_count.begin(); it != file_count.end(); ++it) {
		if (it->second > top_files_count) {
			top_ip = it->first;
			top_files_count = it->second;
		}
	}

	return top_ip;
}

unsigned int FileTable::get_bottom_peer(
		std::map<unsigned int, int>& file_count) {

	int bottom_ip, bottom_files_count;

	bottom_files_count = 0x7FFFFFFF;
	bottom_ip = 0;

	for (auto it = file_count.begin(); it != file_count.end(); ++it) {
		if (it->second < bottom_files_count) {
			bottom_ip = it->first;
			bottom_files_count = it->second;
		}
	}

	return bottom_ip;
}

unsigned int FileTable::get_bottom_peer_wo_file(
		std::map<unsigned int, int>& file_count, std::string file_name) {

	int bottom_ip, bottom_files_count;

	bottom_files_count = 0x7FFFFFFF;
	bottom_ip = 0;

	//printf("File_count size no meio: %d\n",file_count.size());

	for (auto it = file_count.begin(); it != file_count.end(); it++) {

		if (it->second < bottom_files_count	&& (file_table[file_name]->get_location() != it->first)	&& (file_table[file_name]->get_copy_location() != it->first)) {
			bottom_ip = it->first;
			bottom_files_count = it->second;
		}
	}

	return bottom_ip;
}

std::string FileTable::get_balancing_file(unsigned int origin_peer,
		unsigned int dest_peer) {

	std::string file_name;

	for (auto it = file_table.begin(); it != file_table.end(); ++it) {

		if ((it->second->get_location() == origin_peer
				|| it->second->get_copy_location() == origin_peer)
				&& (it->second->get_location() != dest_peer
						&& it->second->get_copy_location() != dest_peer)) {
			file_name = it->first;
			break;
		}
	}
	return file_name;
}

std::vector<std::string> FileTable::get_lost_peer_files(
		unsigned int lostIpAddr) {

	std::vector<std::string> altered_files;

	for (auto it = file_table.begin(); it != file_table.end(); it++) {
		if (it->second->get_location() == lostIpAddr) {
			altered_files.push_back(it->first);
		} else if (it->second->get_copy_location() == lostIpAddr) {
			altered_files.push_back(it->first);
		}
	}

	return altered_files;
}

void FileTable::update_file_table(unsigned int bottom_peer,
		unsigned int top_peer, std::string file_name) {
	if (file_table[file_name]->get_location() == top_peer) {
		file_table[file_name]->set_location(bottom_peer);
	} else {
		file_table[file_name]->set_copy_location(bottom_peer);
	}
}

unsigned int FileTable::get_alternate_file_location(std::string file_name,
		unsigned int lostIpAddr) {
	if (file_table[file_name]->get_location() == lostIpAddr) {
		return file_table[file_name]->get_copy_location();
	} else {
		return file_table[file_name]->get_location();
	}
}
